<template>
  <view class="uqrcode" :class="{ 'uqrcode-hide': hide }" :style="{ width: `${templateOptions.width}px`, height: `${templateOptions.height}px` }">
    <view class="uqrcode-canvas-wrapper">
      <!-- 画布 -->
      <!-- #ifndef APP-NVUE -->
      <canvas class="uqrcode-canvas" :id="canvasId" :canvas-id="canvasId" :type="canvasType" :style="{
          width: `${templateOptions.canvasWidth}px`,
          height: `${templateOptions.canvasHeight}px`,
          transform: templateOptions.canvasTransform
        }" v-if="templateOptions.canvasDisplay" @click="onClick"></canvas>
      <!-- #endif -->

      <!-- nvue用gcanvas -->
      <!-- #ifdef APP-NVUE -->
      <gcanvas class="uqrcode-canvas" ref="gcanvas" :style="{
          width: `${templateOptions.canvasWidth}px`,
          height: `${templateOptions.canvasHeight}px`
        }" v-if="templateOptions.canvasDisplay" @click="onClick"></gcanvas>
      <!-- #endif -->
    </view>

    <!-- 加载效果 -->
    <view class="uqrcode-makeing" v-if="loading === undefined ? makeing : loading">
      <slot name="loading">
        <image class="uqrcode-makeing-image" :style="{ width: `${templateOptions.size / 4}px`, height: `${templateOptions.size / 4}px` }"
          src="">
        </image>
      </slot>
    </view>

    <!-- 错误处理 -->
    <view class="uqrcode-error" v-if="isError" @click="onClick">
      <slot name="error" :error="error">
        <text class="uqrcode-error-message">{{ error.errMsg }}</text>
      </slot>
    </view>

    <!-- H5保存提示 -->
    <!-- #ifdef H5 -->
    <view class="uqrcode-h5-save" v-if="isH5Save">
      <slot name="h5save" :tempFilePath="tempFilePath">
        <image class="uqrcode-h5-save-image" :src="tempFilePath"></image>
        <text class="uqrcode-h5-save-text">{{ h5SaveIsDownload ? '若保存失败，' : '' }}请长按二维码进行保存</text>
      </slot>
      <view class="uqrcode-h5-save-close" @click.stop="isH5Save = false">
        <view class="uqrcode-h5-save-close-before"></view>
        <view class="uqrcode-h5-save-close-after"></view>
      </view>
    </view>
    <!-- #endif -->
  </view>
</template>

<script>
  // #ifdef VUE3
  import {
    toRaw
  } from 'vue';
  // #endif

  /* 引入uQRCode核心js */
  import UQRCode from '../../js_sdk/uqrcode/uqrcode';

  /* 引入nvue所需模块 */
  // #ifdef APP-NVUE
  import {
    enable,
    WeexBridge
  } from '../../js_sdk/gcanvas';
  const modal = weex.requireModule('modal');
  // #endif

  /* 引入队列 */
  import {
    queueDraw,
    queueLoadImage
  } from '../../common/queue';
  /* 引入缓存图片 */
  import {
    cacheImageList
  } from '../../common/cache';

  let instance = null;

  export default {
    name: 'uqrcode',
    props: {
      /**
       * canvas组件id
       */
      canvasId: {
        type: String,
        required: true // canvasId在微信小程序初始值不能为空，created中赋值也不行，必须给一个值，否则挂载组件后无法绘制。不考虑用随机id，uuid
      },
      /**
       * 二维码内容
       */
      value: {
        type: [String, Number]
      },
      /**
       * 选项
       */
      options: {
        type: Object,
        default: () => {
          return {};
        }
      },
      /**
       * 二维码大小
       */
      size: {
        type: [String, Number],
        default: 200
      },
      /**
       * 二维码尺寸单位
       */
      sizeUnit: {
        type: String,
        default: 'px'
      },
      /**
       * 导出的文件类型
       */
      fileType: {
        type: String,
        default: 'png'
      },
      /**
       * 是否初始化组件后就开始生成
       */
      start: {
        type: Boolean,
        default: true
      },
      /**
       * 是否数据发生改变自动重绘
       */
      auto: {
        type: Boolean,
        default: true
      },
      /**
       * 隐藏组件
       */
      hide: {
        type: Boolean,
        default: false
      },
      /**
       * canvas 类型，微信小程序默认使用2d，非2d微信官方已放弃维护，问题比较多
       * 注意：微信小程序type2d手机上正常，PC上微信内打开小程序toDataURL报错，看后期微信官方团队会不会做兼容，不兼容的话只能在自行判断在PC使用非2d，或者直接提示用户请在手机上操作，微信团队的海报中心小程序就是这么做的
       */
      type: {
        type: String,
        default: () => {
          // #ifdef MP-WEIXIN
          return '2d';
          // #endif
          // #ifndef MP-WEIXIN
          return 'normal';
          // #endif
        }
      },
      /**
       * 队列绘制，主要针对NVue端
       */
      queue: {
        type: Boolean,
        default: false
      },
      /**
       * 是否队列加载图片，可减少canvas发起的网络资源请求，节省服务器资源
       */
      isQueueLoadImage: {
        type: Boolean,
        default: false
      },
      /**
       * loading态
       */
      loading: {
        type: Boolean,
        default: undefined
      },
      /**
       * H5保存即自动下载（在支持的环境下），默认false为仅弹层提示用户需要长按图片保存，不会自动下载
       */
      h5SaveIsDownload: {
        type: Boolean,
        default: false
      },
      /**
       * H5下载名称
       */
      h5DownloadName: {
        type: String,
        default: 'uQRCode'
      }
    },
    data() {
      return {
        canvas: undefined,
        canvasType: undefined,
        canvasContext: undefined,
        makeDelegate: undefined,
        drawDelegate: undefined,
        toTempFilePathDelegate: undefined,
        makeExecuted: false,
        makeing: false,
        drawing: false,
        isError: false,
        error: undefined,
        isH5Save: false,
        tempFilePath: '',
        templateOptions: {
          size: 0,
          width: 0, // 组件宽度
          height: 0,
          canvasWidth: 0, // canvas宽度
          canvasHeight: 0,
          canvasTransform: '',
          canvasDisplay: false
        },
        uqrcodeOptions: {
          data: ''
        },
        plugins: [],
        makeingPattern: [
          [
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true]
          ],
          [
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, false, false, false],
            [true, true, true, true, true, true, false, true, true, true],
            [true, true, true, true, true, true, false, true, true, true],
            [true, true, true, true, true, true, false, true, true, true]
          ],
          [
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, true, true, true, true, false, false, false],
            [true, true, true, true, true, true, true, false, false, false],
            [true, true, true, true, true, true, true, false, false, false],
            [true, true, true, false, false, false, false, true, true, true],
            [true, true, true, false, false, false, false, true, true, true]
          ],
          [
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, false, false, false, false, false, false, false],
            [true, true, true, false, false, false, false, false, false, false],
            [true, true, true, false, false, false, false, false, false, false],
            [true, true, true, false, false, false, false, false, false, false],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true],
            [true, true, true, true, true, true, true, true, true, true]
          ]
        ]
      };
    },
    watch: {
      type: {
        handler(val) {
          const types = ['2d'];
          if (types.includes(val)) {
            this.canvasType = val;
          } else {
            this.canvasType = undefined;
          }
        },
        immediate: true
      },
      value: {
        handler() {
          if (this.auto) {
            this.remake();
          }
        }
      },
      size: {
        handler() {
          if (this.auto) {
            this.remake();
          }
        }
      },
      options: {
        handler() {
          if (this.auto) {
            this.remake();
          }
        },
        deep: true
      },
      makeing: {
        handler(val) {
          if (!val) {
            if (typeof this.toTempFilePathDelegate === 'function') {
              this.toTempFilePathDelegate();
            }
          }
        }
      }
    },
    mounted() {
      this.templateOptions.size = this.sizeUnit == 'rpx' ? uni.upx2px(this.size) : this.size;
      this.templateOptions.width = this.templateOptions.size;
      this.templateOptions.height = this.templateOptions.size;
      this.templateOptions.canvasWidth = this.templateOptions.size;
      this.templateOptions.canvasHeight = this.templateOptions.size;
      if (this.canvasType == '2d') {
        // #ifndef MP-WEIXIN
        this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
        this.templateOptions.canvasHeight})`;
        // #endif
      } else {
        this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
        this.templateOptions.canvasHeight})`;
      }
      if (this.start) {
        this.make();
      }
    },
    methods: {
      /**
       * 获取模板选项
       */
      getTemplateOptions() {
        var size = this.sizeUnit == 'rpx' ? uni.upx2px(this.size) : this.size;
        return deepReplace(this.templateOptions, {
          size,
          width: size,
          height: size
        });
      },
      /**
       * 获取插件选项
       */
      getUqrcodeOptions() {
        return deepReplace(this.options, {
          data: String(this.value),
          size: Number(this.templateOptions.size)
        });
      },
      /**
       * 重置画布
       */
      resetCanvas(callback) {
        this.templateOptions.canvasDisplay = false;
        this.$nextTick(() => {
          this.templateOptions.canvasDisplay = true;
          this.$nextTick(() => {
            callback && callback();
          });
        });
      },
      /**
       * 绘制二维码
       */
      async draw(callback = {}, isDrawDelegate = false) {
        if (typeof callback.success != 'function') {
          callback.success = () => {};
        }
        if (typeof callback.fail != 'function') {
          callback.fail = () => {};
        }
        if (typeof callback.complete != 'function') {
          callback.complete = () => {};
        }

        if (this.drawing) {
          if (!isDrawDelegate) {
            this.drawDelegate = () => {
              this.draw(callback, true);
            };
            return;
          }
        } else {
          this.drawing = true;
        }

        if (!this.canvasId) {
          console.error('[uQRCode]: canvasId must be set!');
          this.isError = true;
          this.drawing = false;
          callback.fail({
            errMsg: '[uQRCode]: canvasId must be set!'
          });
          callback.complete({
            errMsg: '[uQRCode]: canvasId must be set!'
          });
          return;
        }
        if (!this.value) {
          console.error('[uQRCode]: value must be set!');
          this.isError = true;
          this.drawing = false;
          callback.fail({
            errMsg: '[uQRCode]: value must be set!'
          });
          callback.complete({
            errMsg: '[uQRCode]: value must be set!'
          });
          return;
        }

        /* 组件数据 */
        this.templateOptions = this.getTemplateOptions();
        /* uQRCode选项 */
        this.uqrcodeOptions = this.getUqrcodeOptions();
        /* 纠错等级兼容字母写法 */
        if (typeof this.uqrcodeOptions.errorCorrectLevel === 'string') {
          this.uqrcodeOptions.errorCorrectLevel = UQRCode.errorCorrectLevel[this.uqrcodeOptions.errorCorrectLevel];
        }
        /* nvue不支持动态修改gcanvas尺寸，除nvue外，默认使用useDynamicSize */
        // #ifndef APP-NVUE
        if (typeof this.options.useDynamicSize === 'undefined') {
          this.uqrcodeOptions.useDynamicSize = true;
        }
        // #endif
        // #ifdef APP-NVUE
        if (typeof this.options.useDynamicSize === 'undefined') {
          this.uqrcodeOptions.useDynamicSize = false;
        }
        // if (typeof this.options.drawReserve === 'undefined') {
        //   this.uqrcodeOptions.drawReserve = true;
        // }
        // #endif

        /* 获取uQRCode实例 */
        const qr = instance = new UQRCode();
        /* 注册扩展 */
        this.plugins.forEach(p => qr.register(p.plugin));
        /* 设置uQRCode选项 */
        qr.setOptions(this.uqrcodeOptions);
        /* 调用制作二维码方法 */
        qr.make();

        /* 获取canvas上下文 */
        let canvasContext = null;
        // #ifndef APP-NVUE
        if (this.canvasType === '2d') {
          // #ifdef MP-WEIXIN
          /* 微信小程序获取canvas2d上下文方式 */
          const canvas = (this.canvas = await new Promise(resolve => {
            uni
              .createSelectorQuery()
              .in(this) // 在组件内使用需要
              .select(`#${this.canvasId}`)
              .fields({
                node: true,
                size: true
              })
              .exec(res => {
                resolve(res[0].node);
              });
          }));
          canvasContext = this.canvasContext = canvas.getContext('2d');
          /* 2d的组件设置宽高与实际canvas绘制宽高不是一个，打个比方，组件size=200，canvas.width设置为100，那么绘制出来就是100=200，组件size=400，canvas.width设置为800，绘制大小还是800=400，所以无需理会下方返回的dynamicSize是多少，按dpr重新赋值给canvas即可 */
          this.templateOptions.canvasWidth = qr.size;
          this.templateOptions.canvasHeight = qr.size;
          this.templateOptions.canvasTransform = '';
          /* 使用dynamicSize+scale，可以解决小块间出现白线问题，dpr可以解决模糊问题 */
          const dpr = uni.getSystemInfoSync().pixelRatio;
          canvas.width = qr.dynamicSize * dpr;
          canvas.height = qr.dynamicSize * dpr;
          canvasContext.scale(dpr, dpr);
          /* 微信小程序获取图像方式 */
          qr.loadImage = this.getLoadImage(function(src) {
            /* 小程序下获取网络图片信息需先配置download域名白名单才能生效 */
            return new Promise((resolve, reject) => {
              const img = canvas.createImage();
              img.src = src;
              img.onload = () => {
                resolve(img);
              };
              img.onerror = err => {
                reject(err);
              };
            });
          });
          // #endif
          // #ifndef MP-WEIXIN
          /* 非微信小程序不支持2d，切换回uniapp获取canvas上下文方式 */
          canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
          /* 使用dynamicSize，可以解决小块间出现白线问题，再通过scale缩放至size，使其达到所设尺寸 */
          this.templateOptions.canvasWidth = qr.dynamicSize;
          this.templateOptions.canvasHeight = qr.dynamicSize;
          this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
          this.templateOptions.canvasHeight})`;
          /* uniapp获取图像方式 */
          qr.loadImage = this.getLoadImage(function(src) {
            return new Promise((resolve, reject) => {
              if (src.startsWith('http')) {
                uni.getImageInfo({
                  src,
                  success: res => {
                    resolve(res.path);
                  },
                  fail: err => {
                    reject(err);
                  }
                });
              } else {
                if (src.startsWith('.')) {
                  console.error('[uQRCode]: 本地图片路径仅支持绝对路径！');
                  throw new Error('[uQRCode]: local image path only supports absolute path!');
                } else {
                  resolve(src);
                }
              }
            });
          });
          // #endif
        } else {
          /* uniapp获取canvas上下文方式 */
          canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
          /* 使用dynamicSize，可以解决小块间出现白线问题，再通过scale缩放至size，使其达到所设尺寸 */
          this.templateOptions.canvasWidth = qr.dynamicSize;
          this.templateOptions.canvasHeight = qr.dynamicSize;
          this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
          this.templateOptions.canvasHeight})`;
          /* uniapp获取图像方式 */
          qr.loadImage = this.getLoadImage(function(src) {
            return new Promise((resolve, reject) => {
              /* getImageInfo在微信小程序的bug：本地路径返回路径会把开头的/或../移除，导致路径错误，解决方法：限制只能使用绝对路径 */
              if (src.startsWith('http')) {
                uni.getImageInfo({
                  src,
                  success: res => {
                    resolve(res.path);
                  },
                  fail: err => {
                    reject(err);
                  }
                });
              } else {
                if (src.startsWith('.')) {
                  console.error('[uQRCode]: 本地图片路径仅支持绝对路径！');
                  throw new Error('[uQRCode]: local image path only supports absolute path!');
                } else {
                  resolve(src);
                }
              }
            });
          });
        }
        // #endif
        // #ifdef APP-NVUE
        /* NVue获取canvas上下文方式 */
        const gcanvas = this.$refs['gcanvas'];
        const canvas = enable(gcanvas, {
          bridge: WeexBridge
        });
        canvasContext = this.canvasContext = canvas.getContext('2d');
        /* NVue获取图像方式 */
        qr.loadImage = this.getLoadImage(function(src) {
          return new Promise((resolve, reject) => {
            /* getImageInfo在nvue的bug：获取同一个路径的图片信息，同一时间第一次获取成功，后续失败，猜测是写入本地时产生文件写入冲突，所以没有返回，特别是对于网络资源 --- 已实现队列绘制，已解决此问题 */
            if (src.startsWith('.')) {
              console.error('[uQRCode]: 本地图片路径仅支持绝对路径！');
              throw new Error('[uQRCode]: local image path only supports absolute path!');
            } else {
              uni.getImageInfo({
                src,
                success: res => {
                  resolve(res.path);
                },
                fail: err => {
                  reject(err);
                }
              });
            }
          });
        });
        // #endif

        /* 设置uQRCode实例的canvas上下文 */
        qr.canvasContext = canvasContext;
        /* 延时等待页面重新绘制完毕 */
        setTimeout(() => {
          /* 从插件获取具体要调用哪一个扩展函数 */
          var plugin = this.plugins.find(p => p.name == qr.style);
          var drawCanvasName = plugin ? plugin.drawCanvas : 'drawCanvas';
          /* 虽然qr[drawCanvasName]是直接返回Promise的，但由于js内部this指向问题，故不能直接exec(qr[drawCanvasName])此方式执行，需要改成exec(() => qr[drawCanvasName]())才能正确获取this */
          var drawCanvas;
          if (this.queue) {
            drawCanvas = () => queueDraw.exec(() => qr[drawCanvasName]());
            // drawCanvas = () => queueDraw.exec(() => new Promise((resolve, reject) => {
            //   setTimeout(() => {
            //     qr[drawCanvasName]().then(resolve).catch(reject);
            //   }, 1000);
            // }));
          } else {
            drawCanvas = () => qr[drawCanvasName]();
          }
          /* 调用绘制方法将二维码图案绘制到canvas上 */
          drawCanvas()
            .then(() => {
              if (this.drawDelegate) {
                /* 高频重绘纠正 */
                let delegate = this.drawDelegate;
                this.drawDelegate = undefined;
                delegate();
              } else {
                this.drawing = false;
                callback.success();
              }
            })
            .catch(err => {
              console.log(err);
              if (this.drawDelegate) {
                /* 高频重绘纠正 */
                let delegate = this.drawDelegate;
                this.drawDelegate = undefined;
                delegate();
              } else {
                this.drawing = false;
                this.isError = true;
                callback.fail(err);
              }
            })
            .finally(() => {
              callback.complete();
            });
        }, 300);
      },
      /**
       * 生成二维码
       */
      make(callback = {}) {
        this.makeExecuted = true;
        this.makeing = true;
        this.isError = false;

        if (typeof callback.success != 'function') {
          callback.success = () => {};
        }
        if (typeof callback.fail != 'function') {
          callback.fail = () => {};
        }
        if (typeof callback.complete != 'function') {
          callback.complete = () => {};
        }

        this.resetCanvas(() => {
          clearTimeout(this.makeDelegate);
          this.makeDelegate = setTimeout(() => {
            this.draw({
              success: () => {
                setTimeout(() => {
                  callback.success();
                  this.complete(true);
                }, 300);
              },
              fail: err => {
                callback.fail(err);
                this.error = err;
                this.complete(false, err.errMsg);
              },
              complete: () => {
                callback.complete();
                this.makeing = false;
              }
            });
          }, 300);
        });
      },
      /**
       * 重新生成
       */
      remake(callback) {
        this.$emit('change');
        this.make(callback);
      },
      /**
       * 生成完成
       */
      complete(success = true, errMsg = '') {
        if (success) {
          this.$emit('complete', {
            success
          });
        } else {
          this.$emit('complete', {
            success,
            errMsg
          });
        }
      },
      /**
       * 导出临时路径
       */
      toTempFilePath(callback = {}) {
        if (typeof callback.success != 'function') {
          callback.success = () => {};
        }
        if (typeof callback.fail != 'function') {
          callback.fail = () => {};
        }
        if (typeof callback.complete != 'function') {
          callback.complete = () => {};
        }

        if (!this.makeExecuted) {
          console.error('[uQRCode]: make() 方法从未调用！请先成功调用 make() 后再进行操作。');
          var err = {
            errMsg: '[uQRCode]: make() method has never been executed! please execute make() successfully before operating.'
          };
          callback.fail(err);
          callback.complete(err);
          return;
        }

        if (this.isError) {
          callback.fail(this.error);
          callback.complete(this.error);
          return;
        }

        if (this.makeing) {
          /* 如果还在生成状态，那当前操作将托管到委托，监听生成完成后再通过委托复调当前方法 */
          this.toTempFilePathDelegate = () => {
            this.toTempFilePath(callback);
          };
          return;
        } else {
          this.toTempFilePathDelegate = null;
        }

        // #ifndef APP-NVUE
        if (this.canvasType === '2d') {
          // #ifdef MP-WEIXIN
          try {
            let dataURL = null;
            // #ifdef VUE3
            dataURL = toRaw(this.canvas)
              .toDataURL();
            // #endif
            // #ifndef VUE3
            dataURL = this.canvas.toDataURL();
            // #endif
            callback.success({
              tempFilePath: dataURL
            });
            callback.complete({
              tempFilePath: dataURL
            });
          } catch (e) {
            callback.fail(e);
            callback.complete(e);
          }
          // #endif
        } else {
          uni.canvasToTempFilePath({
              canvasId: this.canvasId,
              fileType: this.fileType,
              width: Number(this.templateOptions.canvasWidth),
              height: Number(this.templateOptions.canvasHeight),
              destWidth: Number(this.templateOptions.size),
              destHeight: Number(this.templateOptions.size),
              success: res => {
                callback.success(res);
              },
              fail: err => {
                callback.fail(err);
              },
              complete: () => {
                callback.complete();
              }
            },
            this
          );
        }
        // #endif
        // #ifdef APP-NVUE
        const dpr = uni.getSystemInfoSync().pixelRatio;
        this.canvasContext.toTempFilePath(
          0,
          0,
          this.templateOptions.canvasWidth * dpr,
          this.templateOptions.canvasHeight * dpr,
          this.templateOptions.size * dpr,
          this.templateOptions.size * dpr,
          '',
          1,
          res => {
            callback.success(res);
            callback.complete(res);
          }
        );
        // #endif
      },
      /**
       * 保存
       */
      save(callback = {}) {
        if (typeof callback.success != 'function') {
          callback.success = () => {};
        }
        if (typeof callback.fail != 'function') {
          callback.fail = () => {};
        }
        if (typeof callback.complete != 'function') {
          callback.complete = () => {};
        }

        this.toTempFilePath({
          success: res => {
            // #ifndef H5
            if (this.canvasType === '2d') {
              // #ifdef MP-WEIXIN
              /* 需要将 data:image/png;base64, 这段去除 writeFile 才能正常打开文件，否则是损坏文件，无法打开 */
              const reg = new RegExp('^data:image/png;base64,', 'g');
              const dataURL = res.tempFilePath.replace(reg, '');
              const fs = wx.getFileSystemManager();
              const tempFilePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}${
                Math.random()
                  .toString()
                  .split('.')[1]
              }.png`;
              fs.writeFile({
                filePath: tempFilePath, // 要写入的文件路径 (本地路径)
                data: dataURL, // base64图片
                encoding: 'base64',
                success: res1 => {
                  uni.saveImageToPhotosAlbum({
                    filePath: tempFilePath,
                    success: res2 => {
                      callback.success(res2);
                    },
                    fail: err2 => {
                      callback.fail(err2);
                    },
                    complete: () => {
                      callback.complete();
                    }
                  });
                },
                fail: err => {
                  callback.fail(err);
                },
                complete: () => {
                  callback.complete();
                }
              });
              // #endif
            } else {
              uni.saveImageToPhotosAlbum({
                filePath: res.tempFilePath,
                success: res1 => {
                  callback.success(res1);
                },
                fail: err1 => {
                  callback.fail(err1);
                },
                complete: () => {
                  callback.complete();
                }
              });
            }
            // #endif

            // #ifdef H5
            /* 可以在电脑浏览器下载，移动端iOS不行，安卓微信浏览器不行，安卓外部浏览器可以 */
            this.isH5Save = true;
            this.tempFilePath = res.tempFilePath;
            if (this.h5SaveIsDownload) {
              const aEle = document.createElement('a');
              aEle.download = this.h5DownloadName; // 设置下载的文件名，默认是'下载'
              aEle.href = res.tempFilePath;
              document.body.appendChild(aEle);
              aEle.click();
              aEle.remove(); // 下载之后把创建的元素删除
            }
            callback.success({
              errMsg: 'ok'
            });
            callback.complete({
              errMsg: 'ok'
            });
            // #endif
          },
          fail: err => {
            callback.fail(err);
            callback.complete(err);
          }
        });
      },
      /**
       * 注册click事件
       */
      onClick(e) {
        this.$emit('click', e);
      },
      /**
       * 获取实例
       */
      getInstance() {
        return instance;
      },
      /**
       * 注册扩展，组件仅支持注册type为style的drawCanvas扩展
       * @param {Object} plugin
       */
      registerStyle(plugin) {
        if (plugin.Type != 'style') {
          console.warn('[uQRCode]: registerStyle 仅支持注册 style 类型的扩展！');
          return {
            errMsg: 'registerStyle 仅支持注册 style 类型的扩展！'
          };
        }
        if (typeof plugin === 'function') {
          this.plugins.push({
            plugin,
            name: plugin.Name,
            drawCanvas: plugin.DrawCanvas
          });
        }
      },
      getLoadImage(loadImage) {
        var that = this;
        if (typeof loadImage == 'function') {
          return function(src) {
            /* 判断是否是队列加载图片的 */
            if (that.isQueueLoadImage) {
              /* 解决iOS APP||NVUE同时绘制多个二维码导致图片丢失需使用队列 */
              return queueLoadImage.exec(() => {
                return new Promise((resolve, reject) => {
                  setTimeout(() => {
                    const cache = cacheImageList.find(x => x.src == src);
                    if (cache) {
                      resolve(cache.img);
                    } else {
                      loadImage(src)
                        .then(img => {
                          cacheImageList.push({
                            src,
                            img
                          });
                          resolve(img);
                        })
                        .catch(err => {
                          reject(err);
                        });
                    }
                  }, 10);
                });
              });
            } else {
              return loadImage(src);
            }
          };
        } else {
          return function(src) {
            return Promise.resolve(src);
          };
        }
      }
    }
  };

  /**
   * 对象属性深度替换
   * @param {Object} o 原始对象/默认对象/被替换的对象
   * @param {Object} r 从这个对象里取值替换到o对象里
   * @return {Object} 替换后的新对象
   */
  function deepReplace(o = {}, r = {}, c = false) {
    let obj;
    if (c) {
      // 从源替换
      obj = o;
    } else {
      // 不替换源，copy一份备份来替换
      obj = {
        ...o
      };
    }
    for (let k in r) {
      var vr = r[k];
      if (vr != undefined) {
        if (vr.constructor == Object) {
          obj[k] = this.deepReplace(obj[k], vr);
        } else if (vr.constructor == String && !vr) {
          obj[k] = obj[k];
        } else {
          obj[k] = vr;
        }
      }
    }
    return obj;
  }
</script>

<style scoped>
  .uqrcode {
    position: relative;
  }

  .uqrcode-hide {
    position: fixed;
    left: 7500rpx;
  }

  .uqrcode-canvas {
    transform-origin: top left;
  }

  .uqrcode-makeing {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 10;
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    justify-content: center;
    align-items: center;
  }

  .uqrcode-makeing-image {
    /* #ifndef APP-NVUE */
    display: block;
    max-width: 120px;
    max-height: 120px;
    /* #endif */
  }

  .uqrcode-error {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    justify-content: center;
    align-items: center;
  }

  .uqrcode-error-message {
    font-size: 12px;
    color: #939291;
  }

  /* #ifdef H5 */
  .uqrcode-h5-save {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 100;
    background-color: rgba(0, 0, 0, 0.68);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  .uqrcode-h5-save-image {
    width: 512rpx;
    height: 512rpx;
    padding: 32rpx;
  }

  .uqrcode-h5-save-text {
    margin-top: 20rpx;
    font-size: 32rpx;
    font-weight: 700;
    color: #ffffff;
  }

  .uqrcode-h5-save-close {
    position: relative;
    margin-top: 72rpx;
    width: 60rpx;
    height: 60rpx;
    border: 2rpx solid #ffffff;
    border-radius: 60rpx;
    padding: 10rpx;
  }

  .uqrcode-h5-save-close-before {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(45deg);
    width: 40rpx;
    height: 4rpx;
    background: #ffffff;
  }

  .uqrcode-h5-save-close-after {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(-45deg);
    width: 40rpx;
    height: 4rpx;
    background: #ffffff;
  }

  /* #endif */
</style>
